HW 5.2

Author

Eric Lim

Published

April 15, 2023

1 Select Area of Interest: Honolulu, Hawaii

Code
# Bounding Box for Honolulu, Hawaii
bbox <- list(
  p1 = list(long = -158.30892000514868, lat = 21.723556651864115),
  p2 = list(long = -157.6240037655254, lat = 21.22700584076422)
)

# Confirming if bounding box is correct w/ leaflet
leaflet() %>%
  addTiles() %>% 
  addRectangles(
    lng1 = bbox$p1$long, lat1 = bbox$p1$lat,
    lng2 = bbox$p2$long, lat2 = bbox$p2$lat,
    fillColor = "transparent"
  ) %>%
  fitBounds(
    lng1 = bbox$p1$long, lat1 = bbox$p1$lat,
    lng2 = bbox$p2$long, lat2 = bbox$p2$lat,
  )

2 Define Image Size

Code
define_image_size <- function(bbox, major_dim = 400) {
  # calculate aspect ration (width/height) from lat/long bounding box
  aspect_ratio <- abs((bbox$p1$long - bbox$p2$long) / (bbox$p1$lat - bbox$p2$lat))
  # define dimensions
  img_width <- ifelse(aspect_ratio > 1, major_dim, major_dim*aspect_ratio) %>% round()
  img_height <- ifelse(aspect_ratio < 1, major_dim, major_dim/aspect_ratio) %>% round()
  size_str <- paste(img_width, img_height, sep = ",")
  list(height = img_height, width = img_width, size = size_str)
}

image_size <- define_image_size(bbox, major_dim = 600)

3 Get Elevation Data

Code
#https://github.com/wcmbishop/rayshader-demo/blob/master/R/elevation-api.R
get_usgs_elevation_data <- function(bbox, size = "400,400", file = NULL, 
                                    sr_bbox = 4326, sr_image = 4326) {
  require(httr)
  
  url <- parse_url("https://elevation.nationalmap.gov/arcgis/rest/services/3DEPElevation/ImageServer/exportImage")
  res <- GET(
    url, 
    query = list(
      bbox = paste(bbox$p1$long, bbox$p1$lat, bbox$p2$long, bbox$p2$lat,
                   sep = ","),
      bboxSR = sr_bbox,
      imageSR = sr_image,
      size = size,
      format = "tiff",
      pixelType = "F32",
      noDataInterpretation = "esriNoDataMatchAny",
      interpolation = "+RSP_BilinearInterpolation",
      f = "json"
    )
  )
  
  if (status_code(res) == 200) {
    body <- content(res, type = "application/json")
    img_res <- GET(body$href)
    img_bin <- content(img_res, "raw")
    if (is.null(file)) 
      file <- tempfile("elev_matrix", fileext = ".tif")
    writeBin(img_bin, file)
    message(paste("image saved to file:", file))
  } else {
    warning(res)
  }
  invisible(file)
}

# pull elevation data
elev_file <- file.path("elevation.tif")
get_usgs_elevation_data(bbox, size = image_size$size, file = elev_file,
                        sr_bbox = 4326, sr_image = 4326)

# load elevation data
elev_img <- raster::raster(elev_file)
elev_matrix <- matrix(
  raster::extract(elev_img, raster::extent(elev_img), buffer = 1000), 
  nrow = ncol(elev_img), ncol = nrow(elev_img)
)

# calculate rayshader water layer
watermap <- detect_water(elev_matrix)

4 3D Mapping + GIF

Code
#https://github.com/wcmbishop/rayshader-demo/blob/master/R/find-image-coordinates.R
find_image_coordinates <- function(long, lat, bbox, image_width, image_height) {
  x_img <- round(image_width * (long - min(bbox$p1$long, bbox$p2$long)) / abs(bbox$p1$long - bbox$p2$long))
  y_img <- round(image_height * (1 - (lat - min(bbox$p1$lat, bbox$p2$lat)) / abs(bbox$p1$lat - bbox$p2$lat)))
  list(x = x_img, y = y_img)
}

n_frames <- 120 # increase n_frames from 100 to 120
zscale <- 30

# frame transition variables
min_depth_coeff <- 0.5
max_depth_coeff <- 2

waterdepthvalues <- mean(elev_matrix) * min_depth_coeff - (mean(elev_matrix) * (max_depth_coeff - min_depth_coeff)) * cos(seq(0, 2 * pi, length.out = n_frames)) ## adjust water depth range

thetavalues <- -90 + 45 * cos(seq(0, 2*pi, length.out = n_frames))

# labels
label1 <- list(text = "Honolulu")
label1$lon <- -157.8581
label1$lat <- 21.3099

label2 <- list(text = "Pearl City")
label2$lon <- -157.9715
label2$lat <- 21.3910

cat("Heightmap dimensions:", dim(elev_matrix), "\n")
cat("Label1 coordinates:", label1$pos$x, label1$pos$y, "\n")
cat("Label2 coordinates:", label2$pos$x, label2$pos$y, "\n")

# generate .png frame images
img_frames <- paste0("drain", seq_len(n_frames), ".png")
for (i in seq_len(n_frames)) {
  message(paste(" - image", i, "of", n_frames))
  elev_matrix %>%
    sphere_shade(texture = "imhof4") %>%
    add_water(watermap, color = "imhof3") %>%
    plot_3d(elev_matrix, solid = TRUE, shadow = TRUE, zscale = zscale, 
            water = TRUE, watercolor = "imhof3", wateralpha = 0.8, 
            waterlinecolor = "#ffffff", waterlinealpha = 0.5,
            waterdepth = waterdepthvalues[i]/zscale, 
            theta = thetavalues[i], phi = 45)
  
  # find relative image coordinates based on lon, lat of labels
  label1$pos <- find_image_coordinates(label1$lon, label1$lat, bbox, image_size$width, image_size$height) 
  label2$pos <- find_image_coordinates(label2$lon, label2$lat, bbox, image_size$width, image_size$height)  

  render_label(elev_matrix, x = label1$pos$x, y = label1$pos$y, z = 500, 
             zscale = zscale, text = label1$text, textsize = 1, linewidth = 3)
  render_label(elev_matrix, x = label2$pos$x, y = label2$pos$y, z = 500, 
             zscale = zscale, text = label2$text, textsize = 1, linewidth = 3)
  render_snapshot(img_frames[i])
  rgl::clear3d()
}

# build gif
magick::image_write_gif(magick::image_read(img_frames), 
                        path = "water_rise.gif", 
                        delay = 12/n_frames) # increase delay for smoother animation